/**
 * @module transform
 * @description
 * 这个模块包含了所有用于对 `TerrainSchematic` 实例进行几何变换的函数，
 * 例如旋转、翻转、缩放、移动和镜像。
 * 这些函数会改变蓝图中方块的坐标，但通常不改变方块的类型。
 */

import type TerrainSchematic from "../schematic";
import { SchematicBlock } from "../types";
import { getDimensions } from "./analysis";
import { normalizeAndUpdateBounds } from "./geometry";

/**
 * 围绕指定轴心旋转当前蓝图。
 * @param axis 旋转轴（'x', 'y', 或 'z'）。
 * @param angle 旋转角度，单位为度。
 * @returns `this`，允许链式调用。
 */
export function rotate(
  schematic: TerrainSchematic,
  axis: "x" | "y" | "z",
  angle: number
): TerrainSchematic {
  if (angle === 0) {
    return schematic;
  }
  // 对于90度的倍数，使用更快的整数运算
  const isOrthogonal = angle % 90 === 0;
  if (isOrthogonal) {
    const normalizedAngle = ((angle % 360) + 360) % 360;
    if (normalizedAngle === 0) {
      return schematic;
    }
    return rotateOrthogonal(schematic, axis, normalizedAngle as 90 | 180 | 270);
  }

  const angleRad = (angle * Math.PI) / 180;
  const cosA = Math.cos(angleRad);
  const sinA = Math.sin(angleRad);

  const size = getDimensions(schematic);
  const center =
    schematic.pivot ||
    new GameVector3((size.x - 1) / 2, (size.y - 1) / 2, (size.z - 1) / 2);

  const rotatedBlockCoords: { x: number; y: number; z: number; p: number }[] =
    [];

  for (const block of schematic.schematicBlocks) {
    // 1. 平移到原点
    const x = block.x - center.x;
    const y = block.y - center.y;
    const z = block.z - center.z;

    let newX, newY, newZ;

    // 2. 应用旋转
    if (axis === "y") {
      newX = x * cosA - z * sinA;
      newY = y;
      newZ = x * sinA + z * cosA;
    } else if (axis === "x") {
      newX = x;
      newY = y * cosA - z * sinA;
      newZ = y * sinA + z * cosA;
    } else {
      // axis === 'z'
      newX = x * cosA - y * sinA;
      newY = x * sinA + y * cosA;
      newZ = z;
    }

    // 3. 平移回原来的中心
    rotatedBlockCoords.push({
      x: newX + center.x,
      y: newY + center.y,
      z: newZ + center.z,
      p: block.p,
    });
  }

  if (rotatedBlockCoords.length === 0) {
    return schematic;
  }

  // 4. 计算新的边界框
  let minX = rotatedBlockCoords[0].x,
    maxX = rotatedBlockCoords[0].x;
  let minY = rotatedBlockCoords[0].y,
    maxY = rotatedBlockCoords[0].y;
  let minZ = rotatedBlockCoords[0].z,
    maxZ = rotatedBlockCoords[0].z;

  for (const { x, y, z } of rotatedBlockCoords) {
    if (x < minX) {
      minX = x;
    }
    if (x > maxX) {
      maxX = x;
    }
    if (y < minY) {
      minY = y;
    }
    if (y > maxY) {
      maxY = y;
    }
    if (z < minZ) {
      minZ = z;
    }
    if (z > maxZ) {
      maxZ = z;
    }
  }

  // 5. 归一化坐标并四舍五入到最近的整数，同时处理哈希碰撞
  const newBlocksMap = new Map<
    string,
    { x: number; y: number; z: number; p: number }
  >();
  for (const block of rotatedBlockCoords) {
    const newBlock = {
      x: Math.round(block.x - minX),
      y: Math.round(block.y - minY),
      z: Math.round(block.z - minZ),
      p: block.p,
    };
    const key = `${newBlock.x}:${newBlock.y}:${newBlock.z}`;
    newBlocksMap.set(key, newBlock);
  }
  schematic.schematicBlocks = Array.from(newBlocksMap.values());

  // 6. 更新数据中的边界
  const newSize = new GameVector3(
    Math.round(maxX - minX),
    Math.round(maxY - minY),
    Math.round(maxZ - minZ)
  );

  schematic.data.bounds = new GameBounds3(new GameVector3(0, 0, 0), newSize);
  return schematic;
}

/**
 * 围绕指定轴心以90度为单位旋转当前蓝图
 */
function rotateOrthogonal(
  schematic: TerrainSchematic,
  axis: "x" | "y" | "z",
  angle: 90 | 180 | 270
): TerrainSchematic {
  const size = getDimensions(schematic);
  const pivot =
    schematic.pivot ||
    new GameVector3((size.x - 1) / 2, (size.y - 1) / 2, (size.z - 1) / 2);

  // 旋转调色板中的方块（仅限Y轴）
  if (axis === "y") {
    const palette: { id: voxelId; rot: voxelRotation }[] = [];
    for (let i = 0; i < schematic.data.palette.length; i += 2) {
      palette.push({
        id: schematic.data.palette[i] as voxelId,
        rot: schematic.data.palette[i + 1] as voxelRotation,
      });
    }
    const rotationAmount = angle / 90;
    const rotatedPalette = palette.map((block) => ({
      ...block,
      rot: ((block.rot + rotationAmount) % 4) as voxelRotation,
    }));
    schematic.data.palette = rotatedPalette.flatMap((p) => [p.id, p.rot]);
  }

  const newBlocks: { x: number; y: number; z: number; p: number }[] = [];
  // 直接在方块列表上进行坐标变换
  for (const block of schematic.schematicBlocks) {
    const p = new GameVector3(block.x, block.y, block.z);
    // 平移到原点
    p.sub(pivot);

    // 旋转
    const { x, y, z } = p;
    if (axis === "y") {
      if (angle === 90) {
        p.x = -z;
        p.z = x;
      } else if (angle === 180) {
        p.x = -x;
        p.z = -z;
      } else if (angle === 270) {
        p.x = z;
        p.z = -x;
      }
    } else if (axis === "x") {
      if (angle === 90) {
        p.y = -z;
        p.z = y;
      } else if (angle === 180) {
        p.y = -y;
        p.z = -z;
      } else if (angle === 270) {
        p.y = z;
        p.z = -y;
      }
    } else if (axis === "z") {
      if (angle === 90) {
        p.x = -y;
        p.y = x;
      } else if (angle === 180) {
        p.x = -x;
        p.y = -y;
      } else if (angle === 270) {
        p.x = y;
        p.y = -x;
      }
    }

    // 平移回去
    p.add(pivot);

    newBlocks.push({ x: p.x, y: p.y, z: p.z, p: block.p });
  }

  // 因为自定义轴心可能导致坐标变为浮点数或负数，我们需要归一化
  const finalBlocksMap = new Map<
    string,
    { x: number; y: number; z: number; p: number }
  >();
  for (const b of newBlocks) {
    const newBlock = {
      x: Math.round(b.x),
      y: Math.round(b.y),
      z: Math.round(b.z),
      p: b.p,
    };
    finalBlocksMap.set(`${newBlock.x}:${newBlock.y}:${newBlock.z}`, newBlock);
  }

  schematic.schematicBlocks = Array.from(finalBlocksMap.values());
  normalizeAndUpdateBounds(schematic);

  return schematic;
}

/**
 * 沿指定轴的中心线镜像结构，创建完美的对称性。
 * 此操作会保留一侧的结构，并用其镜像替换另一侧。
 * @param axis 镜像轴 ('x', 'y', 或 'z')。
 * @param keepPositiveSide 可选，默认为 `false`。如果为 `true`，则保留正坐标轴一侧的结构并镜像到负坐标轴一侧；否则，保留负半轴一侧。
 * @returns `this`，允许链式调用。
 */
export function mirror(
  schematic: TerrainSchematic,
  axis: "x" | "y" | "z",
  keepPositiveSide = false
): TerrainSchematic {
  if (schematic.schematicBlocks.length === 0) {
    return schematic;
  }

  const size = getDimensions(schematic);
  const pivot =
    schematic.pivot ||
    new GameVector3((size.x - 1) / 2, (size.y - 1) / 2, (size.z - 1) / 2);
  const center = pivot[axis];

  // 1. 过滤方块，只保留一侧，并创建一个Map以便快速查找
  const blocksToKeep = new Map<
    string,
    { x: number; y: number; z: number; p: number }
  >();
  schematic.schematicBlocks
    .filter((block: SchematicBlock) => {
      return keepPositiveSide ? block[axis] >= center : block[axis] <= center;
    })
    .forEach((block: SchematicBlock) => {
      blocksToKeep.set(`${block.x},${block.y},${block.z}`, block);
    });

  // 2. 遍历保留的方块，创建它们的镜像副本
  const mirroredBlocks = new Map<
    string,
    { x: number; y: number; z: number; p: number }
  >();
  for (const block of blocksToKeep.values()) {
    const mirroredBlock = { ...block };
    // 镜像公式: new_coord = center + (center - old_coord) = 2 * center - old_coord
    mirroredBlock[axis] = Math.round(2 * center - block[axis]);
    mirroredBlocks.set(
      `${mirroredBlock.x},${mirroredBlock.y},${mirroredBlock.z}`,
      mirroredBlock
    );
  }

  // 3. 将原始保留的方块和新创建的镜像方块合并成最终的列表
  // 使用Map可以自动处理中心线上的方块（因为键是唯一的）
  const finalBlocksMap = new Map([...blocksToKeep, ...mirroredBlocks]);
  schematic.schematicBlocks = Array.from(finalBlocksMap.values());

  // 4. 镜像后可能会缩小边界，所以归一化是必要的。
  normalizeAndUpdateBounds(schematic);
  // optimizePalette(schematic); // 可能会有方块类型被完全移除

  return schematic;
}

/**
 * 缩放当前蓝图。
 * @param scale 缩放因子xyz（必须是正整数）
 * @returns `this`，允许链式调用。
 * @throws 如果缩放因子不是正整数，则抛出错误。
 */
export function scale(
  schematic: TerrainSchematic,
  scale: GameVector3
): TerrainSchematic {
  if (
    !Number.isInteger(scale.x) ||
    !Number.isInteger(scale.y) ||
    !Number.isInteger(scale.z)
  ) {
    throw new Error("缩放因子必须是整数");
  }

  if (scale.x === 1 && scale.y === 1 && scale.z === 1) {
    return schematic; // 无需缩放
  }

  const size = getDimensions(schematic);
  const pivot =
    schematic.pivot ||
    new GameVector3((size.x - 1) / 2, (size.y - 1) / 2, (size.z - 1) / 2);

  // 创建新的方块列表
  const scaledBlocks: { x: number; y: number; z: number; p: number }[] = [];

  for (const block of schematic.schematicBlocks) {
    // 1. 平移到轴心点
    const translatedX = block.x - pivot.x;
    const translatedY = block.y - pivot.y;
    const translatedZ = block.z - pivot.z;

    // 2. 缩放
    const scaledX = translatedX * scale.x;
    const scaledY = translatedY * scale.y;
    const scaledZ = translatedZ * scale.z;

    // 3. 平移回去并为每个原始方块创建缩放后的方块群
    for (let dx = 0; dx < scale.x; dx++) {
      for (let dy = 0; dy < scale.y; dy++) {
        for (let dz = 0; dz < scale.z; dz++) {
          scaledBlocks.push({
            x: pivot.x + scaledX + dx,
            y: pivot.y + scaledY + dy,
            z: pivot.z + scaledZ + dz,
            p: block.p,
          });
        }
      }
    }
  }

  // 使用Map去重，因为某些情况下（例如缩小）可能会产生重叠方块
  const finalBlocksMap = new Map<
    string,
    { x: number; y: number; z: number; p: number }
  >();
  for (const b of scaledBlocks) {
    const newBlock = {
      x: Math.round(b.x),
      y: Math.round(b.y),
      z: Math.round(b.z),
      p: b.p,
    };
    finalBlocksMap.set(`${newBlock.x}:${newBlock.y}:${newBlock.z}`, newBlock);
  }

  schematic.schematicBlocks = Array.from(finalBlocksMap.values());

  // 更新边界框和原点
  normalizeAndUpdateBounds(schematic);

  return schematic;
}

/**
 * 沿X轴或Z轴翻转（镜像）当前蓝图。
 * @param axis 翻转轴（'x' 或 'z'）。
 * @returns `this`，允许链式调用。
 * @throws 如果内部数据无效，则抛出错误。
 */
export function flip(
  schematic: TerrainSchematic,
  axis: "x" | "z"
): TerrainSchematic {
  if (!schematic.data.bounds) {
    throw new Error("无效的蓝图数据，缺少边界或调色板信息。");
  }
  const size = getDimensions(schematic);
  const pivot =
    schematic.pivot ||
    new GameVector3((size.x - 1) / 2, (size.y - 1) / 2, (size.z - 1) / 2);

  for (const block of schematic.schematicBlocks) {
    // 翻转公式: new_coord = pivot + (pivot - old_coord) = 2 * pivot - old_coord
    block[axis] = 2 * pivot[axis] - block[axis];
  }

  // 翻转可能导致坐标变为浮点或移出原始边界，需要归一化
  normalizeAndUpdateBounds(schematic);
  return schematic;
}

/**
 * 设置一个自定义的变换轴心点。
 * 这个轴心点将作为所有几何变换（旋转、缩放、镜像、翻转）和放置（导入）操作的中心基准。
 * - **旋转/缩放/镜像/翻转**: 所有这些操作都将围绕此点进行，而不是默认的几何中心。
 * - **导入**: 调用 .import(position) 时，系统会将这个轴心点精确地放置在 `position` 坐标上。
 * @param pivot 期望的轴心点的局部坐标 (相对于蓝图的(0,0,0)点) 或全局世界坐标。
 * @param isGlobal 如果为 `true`，则会将传入的 `pivot` (应为世界坐标) 自动转换成正确的局部坐标。默认为 `false`。
 * @returns `this`，允许链式调用。
 */
export function setPivot(
  schematic: TerrainSchematic,
  pivot: GameVector3,
  isGlobal: boolean = false
): TerrainSchematic {
  schematic.pivot = !isGlobal ? pivot : pivot.sub(schematic.origin);
  return schematic;
}

/**
 * 清除自定义轴心，恢复到默认的几何中心。
 * @returns `this`，允许链式调用。
 */
export function clearPivot(schematic: TerrainSchematic): TerrainSchematic {
  schematic.pivot = null;
  return schematic;
}

/**
 * 沿X, Y, Z轴将当前结构进行三维堆叠，形成一个更大的阵列。
 * @param schematic 要操作的蓝图
 * @param counts 一个GameVector3对象，其x, y, z属性分别代表三个方向上的堆叠总数。例如 {x: 2, y: 3, z: 1} 将创建一个2x3x1的结构阵列。
 * @returns `this`，允许链式调用。
 */
export function stack(
  schematic: TerrainSchematic,
  counts: GameVector3
): TerrainSchematic {
  if (
    schematic.schematicBlocks.length === 0 ||
    !Number.isInteger(counts.x) ||
    !Number.isInteger(counts.y) ||
    !Number.isInteger(counts.z) ||
    counts.x < 1 ||
    counts.y < 1 ||
    counts.z < 1
  ) {
    return schematic; // 无效输入或无需操作
  }

  if (counts.x === 1 && counts.y === 1 && counts.z === 1) {
    return schematic; // 无需堆叠
  }

  const originalBlocks = [...schematic.schematicBlocks];
  const size = getDimensions(schematic);
  const allNewBlocks: { x: number; y: number; z: number; p: number }[] = [];

  for (let i = 0; i < counts.x; i++) {
    for (let j = 0; j < counts.y; j++) {
      for (let k = 0; k < counts.z; k++) {
        // 跳过原始位置 (0,0,0) 的复制
        if (i === 0 && j === 0 && k === 0) {
          continue;
        }

        for (const block of originalBlocks) {
          const newBlock = {
            x: block.x + i * size.x,
            y: block.y + j * size.y,
            z: block.z + k * size.z,
            p: block.p,
          };
          allNewBlocks.push(newBlock);
        }
      }
    }
  }

  schematic.schematicBlocks.push(...allNewBlocks);

  // 更新边界框
  const newSize = new GameVector3(
    size.x * counts.x,
    size.y * counts.y,
    size.z * counts.z
  );

  schematic.data.bounds = new GameBounds3(
    new GameVector3(0, 0, 0),
    newSize.sub(new GameVector3(1, 1, 1))
  );

  return schematic;
}
